#!/bin/bash
# Copyright (c) 2014 The Native Client Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

# Package installer script for use inside the devenv environment.
# Assumes setup-environment has been source at least once in the parent
# environment.
# Written to avoid using anything other than bash, geturl, and unzip when
# installing core packages.

set -o errexit
set -o nounset
shopt -s nullglob

STORAGE_URL=${STORAGE_URL:-https://naclports.storage.googleapis.com/builds}
SDK_VERSION=${SDK_VERSION:-pepper_44}

# This default version for packages that are downloaded and installed.
# To update this use 'git describe <rev>' to get the revision string.  Be
# sure to wait until the bot have build all the packages for a given
# revision before changing this value.

REVISION=${REVISION:-trunk-437-g4255d10}
DEFAULT_SOURCE="${STORAGE_URL}/${SDK_VERSION}/${REVISION}/publish"
VERBOSE=0

NACL_ARCH_ALT="${NACL_ARCH}"
if [[ "${TOOLCHAIN}" == "pnacl" ]]; then
  NACL_ARCH_ALT="pnacl"
fi

# Locations for package archives.
# Prefer this one going forward:
PACKAGE_BY_ARCH=${TOOLCHAIN}/${NACL_ARCH_ALT}.zip
# Old default:
PACKAGE_AS_ONE=${TOOLCHAIN}.zip

# List of packages (keep sorted).
# Pairs of <package-name>:<status><package-subdir>[:<source>:<alt-subdir>]
PACKAGE_INFO="\
blackbox|WIP|${PACKAGE_BY_ARCH} \
busybox|WIP|glibc/busybox-1.22.0.zip \
bzip2|ok|${PACKAGE_BY_ARCH} \
civitweb|WIP|${PACKAGE_AS_ONE} \
coreutils|ok|${PACKAGE_BY_ARCH} \
curl|ok|${PACKAGE_AS_ONE} \
drod|WIP|${PACKAGE_AS_ONE} \
emacs|ok|glibc/emacs-24.3.zip \
findutils|ok|${PACKAGE_BY_ARCH} \
fvwm|ok|${PACKAGE_BY_ARCH} \
gawk|ok|${PACKAGE_BY_ARCH} \
gforth|ok|${PACKAGE_AS_ONE} \
git|ok|${PACKAGE_BY_ARCH} \
grep|ok|${PACKAGE_BY_ARCH} \
less|ok|${PACKAGE_BY_ARCH} \
lua5.1|ok|${PACKAGE_BY_ARCH} \
lua5.2|ok|${PACKAGE_BY_ARCH} \
m4|ok|${PACKAGE_BY_ARCH} \
make|ok|${PACKAGE_BY_ARCH} \
mingn.base|ok|glibc/tarballs/base.${NACL_ARCH}.zip \
mingn.lib|ok|glibc/tarballs/lib.all.zip \
nano|ok|${PACKAGE_AS_ONE} \
nethack|ok|${PACKAGE_AS_ONE} \
ninja|WIP|${PACKAGE_BY_ARCH} \
openssh|ok|glibc.zip \
python3|WIP|${TOOLCHAIN}/python.zip \
python|ok|${TOOLCHAIN}/python.zip \
ruby|ok|${TOOLCHAIN}/ruby.zip \
sqlite|ok|${PACKAGE_AS_ONE} \
subversion|WIP|${PACKAGE_BY_ARCH} \
tar|WIP|${PACKAGE_AS_ONE} \
texlive|ok|${PACKAGE_BY_ARCH} \
thttpd|ok|glibc.zip \
tk|ok|glibc/${NACL_ARCH_ALT}.zip \
toybox|WIP|${TOOLCHAIN}/toybox-0.4.7.zip \
twm|ok|glibc/${NACL_ARCH_ALT}.zip \
vim|ok|${PACKAGE_AS_ONE} \
xeyes|ok|glibc/${NACL_ARCH_ALT}.zip \
xz|ok|${PACKAGE_BY_ARCH} \
"

ALIASES="\
lua|lua5.2
"

RemoveDir() {
  local path="$1"
  if [ -d ${path} ]; then
    rm -rf ${path}/* || true
    rm -rf ${path}
  fi
}

#
# $1 - package name
#
InstallPackage() {
  local package="$1"
  local subdir="${PACKAGE_BY_ARCH}"
  local source_base="${PACKAGE_SOURCE}"

  # Replace package aliases with alternates.
  for names in ${ALIASES}; do
    local names_array=(${names//|/ })
    if [ "${package}" = "${names_array[0]}" ]; then
      package="${names_array[1]}"
    fi
  done

  for info in ${PACKAGE_INFO}; do
    local info_array=(${info//|/ })
    if [ "${package}" = "${info_array[0]}" ]; then
      subdir="${info_array[2]}"
      if [[ "${ALTERNATE_SOURCE}" == "0" && \
            ${#info_array[@]} > 3 && \
            "${info_array[3]}" != "" ]]; then
        source_base="${info_array[3]}"
        subdir="${info_array[4]}"
      fi
    fi
  done

  local url=${source_base}/${package}/${subdir}
  InstallPackageFromUrl ${package} ${url}
}

#
# $1 - package name
# $2 - zip file url
#
InstallPackageFromUrl() {
  local package=$1
  local url=$2
  local package_dir=/mnt/html5/packages/${package}.${NACL_ARCH}
  local clobber=1

  # Special case for MinGN.
  if [[ "${package}" = "mingn.base" || "${package}" = "mingn.lib" ]]; then
    url="${PACKAGE_SOURCE}/mingn/${subdir}"
    package_dir="/mnt/html5"
    clobber=0
  fi
  # Special case for coreutils.
  if [[ "${package}" = "coreutils" ]]; then
    package_dir="/mnt/html5/coreutils.${NACL_ARCH}"
    clobber=0
  fi

  local stamp_dir=/mnt/html5/stamps/${package}.${NACL_ARCH}
  local url_stamp="${stamp_dir}/${url//\//_}"

  if [ -d ${package_dir}/${package} ]; then
    local package_inside=${package_dir}/${package}
  else
    local package_inside=${package_dir}
  fi

  if [[ ${PACKAGE_FORCE} != 1 && -d ${url_stamp} ]]; then
    # Package is up to date.
    return
  fi

  if [ -d ${stamp_dir} ]; then
    echo "Updating ${package} package..."
  else
    echo "Installing ${package} package..."
  fi
  local archive=/tmp/devenv.${package}.zip
  echo "Downloading ${url}..."
  if ! geturl ${url} ${archive}; then
    echo "geturl failed"
    exit 1
  fi

  echo "Extracting ${package}..."
  if [ ! -e ${archive} ]; then
    echo "${archive} does not exist!"
    exit 1
  fi
  if [ "${clobber}" = 1 ]; then
    RemoveDir ${package_dir}
  fi
  if [ ! -d ${package_dir%/*} ]; then
    mkdir -p ${package_dir}
  fi
  local unzip_flags=
  if [ ${VERBOSE} != "1" ]; then
    unzip_flags=-q
  fi
  if ! unzip -o $unzip_flags -d ${package_dir} ${archive}; then
    echo "error: unzip failed"
    exit 1
  fi
  rm ${archive}

  # Update package_inside.
  if [ -d ${package_dir}/${package} ]; then
    package_inside=${package_dir}/${package}
  else
    package_inside=${package_dir}
  fi

  # TODO(bradnelson): Do something better.
  # Rename appropriate nexe/pexe to unsuffixed version.
  local suffixes=".pexe _pnacl.pexe _${NACL_ARCH}.nexe"
  local platform_dirs="_platform_specific/${NACL_ARCH}/ \
                       _platform_specific/all/"
  if [ "${NACL_ARCH}" = "i686" ]; then
    suffixes+=" _x86_32.nexe"
    platform_dirs+=" _platform_specific/x86_32/"
  fi
  for alt in "" _ppapi; do
    for plat in "" ${platform_dirs}; do
      for suffix in ${suffixes}; do
        local exe=${package_inside}/${plat}${package}${alt}${suffix}
        if [ -e ${exe} ]; then
          cp ${exe} ${package_inside}/${package}
        fi
      done
    done
  done

  # Remove out stamps and write out new one.
  RemoveDir ${stamp_dir}
  mkdir -p ${url_stamp}
  echo "Done."
}

function Usage() {
  echo "Usage: $0 [options]"
  echo "  -i <package> = install package"
  echo "  -s <source> = alternate package source"
  echo "  -L = local package source (use with make run in naclports)"
  echo "  -f = force install"
  echo "  -l = list known packages"
  echo "  -h = help"
  exit 1
}

function main() {
  PACKAGE_LIST=""
  PACKAGE_FORCE=0
  ALTERNATE_SOURCE=0

  while getopts "vs:i:hLfl" o; do
    case "${o}" in
      i)
        PACKAGE_LIST+=" ${OPTARG}"
        ;;
      s)
        PACKAGE_SOURCE="${OPTARG}"
        ;;
      L)
        PACKAGE_SOURCE="http://localhost:5103/"
        ;;
      f)
        PACKAGE_FORCE=1
        ;;
      v)
        VERBOSE=1
        ;;
      l)
        NEXT_COL="\\r\\x1b[30C"
        echo -e "PACKAGE${NEXT_COL}STATUS"
        echo -e "-------${NEXT_COL}------"
        for info in ${PACKAGE_INFO}; do
          local info_array=(${info//|/ })
          echo -e "${info_array[0]}${NEXT_COL}${info_array[1]}"
        done
        exit 0
        ;;
      *)
        Usage
        ;;
    esac
  done
  shift $((OPTIND-1))

  if [ "$*" != "" ]; then
    echo "Bad Options: $*"
    Usage
  fi

  if [ "${PACKAGE_LIST}" = "" ]; then
    echo "No packages selected."
    Usage
  fi

  if [ -z "${NACL_ARCH:-}" ]; then
    echo "error: NACL_ARCH not set"
    exit 1
  fi

  if [ -z "${TOOLCHAIN:-}" ]; then
    echo "error: TOOLCHAIN not set"
    exit 1
  fi

  if [ "${PACKAGE_SOURCE:-}" = "" ]; then
    PACKAGE_SOURCE="${PACKAGE_SOURCE:-${DEFAULT_SOURCE}}"
    ALTERNATE_SOURCE=1
  fi

  for package in ${PACKAGE_LIST}; do
    InstallPackage ${package}
  done
}

main "$@"
